package samplejava.compiler;
//import java.util.Collections;
import java.util.Stack;
//import java.util.ArrayList;

public class SymTable {
	
	private int blockLevel; //actual nivel de profundidad
	private Stack<EntryTS> ts; //tabla de simbolos. CUIDADO: el top es el ULTIMO
	//private ArrayList<EntryTR> tr; 	//RwTable tr; //tabla de palabras reservadas
		
	//contadores de desplazamiento
	private int gOffset; //globalSize 
	private int lOffset; //localSize
	private int pOffset;
	//boolean isGlobal = true; //para saber si en data_def incrementar gOffset o lOffset
	
	
	public SymTable() { //inicializacion
		init();
		pOffset = -1;
	}
	
	//funciones de mas bajo nivel:
	
	public EntryTS insert(String name, int kind, long value, int tipo) {
		EntryTS entrada = new EntryTS(name, kind, this.getBlockLevel(), value, tipo);
		if (pOffset == 0) pOffset = -1;
		
		//System.out.println("\nINSERT, entrada = " + entrada + "\npOffset=" + pOffset);
        if (this.getBlockLevel() == 0) {
    		entrada.setOffset(gOffset);
    		
    		switch (tipo) {
    		case Tipos.LONG: gOffset += 2; break;
    		case Tipos.FLOAT: gOffset += 4; break;
    		default: gOffset++; break; //Tipos.INT
    		}

        }
        else
        	
        	if ((kind == EntryTS.PARAM ) || (kind == EntryTS.PARAM_REF)) {
        		entrada.setOffset(pOffset);
        		
        		switch (tipo) {
        		case Tipos.LONG: pOffset += 2; break;
        		case Tipos.FLOAT: pOffset += 4; break;
        		default: pOffset++; break; //Tipos.INT
        		}
        	}
        	else { //EntryTS.VAR
        		entrada.setOffset(lOffset);

        		switch (tipo) {
        		case Tipos.LONG: lOffset += 2; break;
        		case Tipos.FLOAT: lOffset += 4; break;
        		default: lOffset++; break; //Tipos.INT
        		}        		
        	}
        
        ts.push(entrada); //CUIDADO: inserta al FINAL
		return entrada;
	}
	
	public EntryTS insert(String name, int kind, ETipo etipo) {
		EntryTS entrada = new EntryTS(name, kind, this.getBlockLevel(), etipo);
		if (pOffset == 0) pOffset = -1;

		//System.out.println("\nINSERT, entrada = " + entrada + "\npOffset=" + pOffset);
        if (this.getBlockLevel() == 0) {
    		entrada.setOffset(gOffset);

    		switch (etipo.getIdTipo()) {
    		case Tipos.LONG: gOffset += 2; break;
    		case Tipos.FLOAT: gOffset += 4; break;
    		default: gOffset++; break; //Tipos.INT
    		}
    		
        }
        else
        	
        	if ((kind == EntryTS.PARAM ) || (kind == EntryTS.PARAM_REF)) {
        		entrada.setOffset(pOffset);

        		switch (etipo.getIdTipo()) {
        		case Tipos.LONG: pOffset += 2; break;
        		case Tipos.FLOAT: pOffset += 4; break;
        		default: pOffset++; break; //Tipos.INT
        		}
        	}
        	else { //EntryTS.VAR
        		entrada.setOffset(lOffset);

        		switch (etipo.getIdTipo()) {
        		case Tipos.LONG: lOffset += 2; break;
        		case Tipos.FLOAT: lOffset += 4; break;
        		default: lOffset++; break; //Tipos.INT
        		}
        	}
        
		ts.push(entrada); //CUIDADO: inserta al FINAL        
		return entrada;
	}
	
	public EntryTS find(String name) { //busca en toda la TS. encuentra la del bloque mas cercano
		/*for (EntryTS entrada : ts) { //busca en tabla de simbolos
			if (entrada.getName().equals(name))
				return entrada;
		}*/
		if (ts.size() == 0)
			return null;
		
		int i = ts.size() - 1; //desde el final (top) hasta el principio (fondo)
	    while ((! ts.get(i).getName().equals(name)) && (i > 0)) {
	    	i--;
	    }
	    
	    if (!(ts.get(i).getName().equals(name)))
	    	return null; //no encontrado. EntryTS() nula
	    
	    return ts.get(i); //encontrado
	}
	
	public EntryTS findBlock(String name) { //busca solo en el bloque actual
		if (ts.size() == 0)
			return null;
		
		int i = ts.size() - 1; //desde el final (top) hasta el principio (fondo)
		
		while ((ts.get(i).getBlockLevel() == this.getBlockLevel()) && (i > 0)) {
			if (ts.get(i).getName().equals(name))
				return ts.get(i);
			
			i--;
		}
		
		return null; //no encontrado. EntryTS() nula
	}
	
	
	public EntryTS lookUp(String name) {
		EntryTS entrada = find(name); //antes: findBlock
		if (entrada == null) { //no encontrado. getKind() == EntryTS.NIL
			//entrada = new EntryTS(name, EntryTS.UDEC, this.getBlockLevel(), (long) 0);
			entrada = insert(name, EntryTS.UDEC, 0, 0);
		}
		
		return entrada; //en cualquier caso devuelve entrada
	}
	
	public EntryTS lookUp(String name, int kind, long value, int tipo) {
		EntryTS entrada = find(name);
		if (entrada == null)  //no encontrado. getKind() == EntryTS.NIL
			entrada = insert(name, kind, value, tipo);
		//XXX: else: controlar error sintactico: redefinicion en mismo bloque
		
		return entrada; //en cualquier caso devuelve entrada
	}
	
	public EntryTS lookUp(String name, int kind, ETipo etipo) {
		EntryTS entrada = find(name);
		if (entrada == null)  //no encontrado. getKind() == EntryTS.NIL
			entrada = insert(name, kind, etipo);
		//XXX: else: controlar error sintactico: redefinicion en mismo bloque
		
		return entrada; //en cualquier caso devuelve entrada
	}
	
	/*public int binary(String name) { //busca en tabla reservadas y si no lookUp. devuelve token
	    //TODO busqueda binaria
		return Tokens.ID; //no encontrado en reservadas, si no en TS
	}*/
	
	//funciones de mas alto nivel:
	public void createLocal(String name, int tipo) {
		lookUp(name, EntryTS.UDEC, 0, tipo);
	}
	
	public EntryTS modify(String name, int kind, long value, int tipo) {
	    for (int i = ts.size() - 1; i >= 0; i--)
	    	if (ts.get(i).getName().equals(name)) {//si es la entrada encontrada
	    		EntryTS entrada = ts.get(i);
	    		entrada.setKind(kind);
	    		entrada.setValue(value);
	    		entrada.setTipo(tipo);
	    		ts.set(i, entrada);
	    		return entrada;
	    	}
	    	
		return null; //no encontrada
	}
	
	public EntryTS modifyValue(String name, long value, long decimal) {
	    for (int i = ts.size() - 1; i >= 0; i--)
	    	if (ts.get(i).getName().equals(name)) { //si es la entrada encontrada
	    		EntryTS entrada = ts.get(i);
	    		entrada.setValue(value);
	    		entrada.setDecimalValue(decimal);
	    		ts.set(i, entrada);
	    		return entrada;
	    	}
	    	
		return null; //no encontrada
	}
	
	public long findValue(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return EntryTS.NIL;
		}
		
		return entrada.getValue();
	}
	
	public int findOffset(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return EntryTS.NIL;
		}
		
		return entrada.getOffset();
	}
	
	public int findTipo(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return EntryTS.NIL;
		}
		
		return entrada.getTipo();
	}
	
	public ETipo findETipo(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return new ETipo_Error();
		}
		
		return entrada.getETipo();
	}
	
	public int findSize(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return EntryTS.NIL;
		}
		
		return entrada.getSize();
	}
	
	public int findKind(String name) {
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return EntryTS.NIL;
		}
		
		return entrada.getKind();
	}
	
	public boolean isGlobal(String name) { //devuelve true si es variable global
		EntryTS entrada = this.find(name);
		if (entrada == null) {
			System.out.println("Error semantico: " + name + " no se ha declarado");
			return false; //CUIDADO: interpreta que es variable local. 
		}
		
		return entrada.isGlobal();
	}
	
	
	public void init() {
		this.setBlockLevel(0); //empieza bloque global
		ts = new Stack<EntryTS>();
		//tr = new ArrayList<EntryTR>();
		
		gOffset = 0;
		lOffset = 0;
		pOffset = -1;
		
		insert("main", EntryTS.UDEC, 0, 0); //inserta funcion main en bloque global
		
		//inicializacion de palabras reservadas
		/*tr.add(new EntryTR("break", Tokens.BREAK));
		tr.add(new EntryTR("continue", Tokens.CONTINUE));
		tr.add(new EntryTR("else", Tokens.ELSE));
		tr.add(new EntryTR("if", Tokens.IF));
		tr.add(new EntryTR("int", Tokens.INT));
		tr.add(new EntryTR("long", Tokens.LONG));
		tr.add(new EntryTR("while", Tokens.WHILE));
		tr.add(new EntryTR("return", Tokens.RETURN));
		//Collections.sort(tr); //ordenada reservadas para hacer luego busqueda binaria */
	}
	
	public void move() {
		//XXX
	}
	
	public void blkPush() {
		this.blockLevel++;
		this.lOffset = 0;
		this.pOffset = 0;
	}
	
	public void blkPop() {
		//aqui se garantiza que ts.size() > 0
		//aqui se garantiza que blockLevel > 0. al terminar quedara >= 0
	    int i = ts.size() - 1;
	    while ((ts.get(i).getBlockLevel() == this.getBlockLevel()) && (i > 0)) {
	    	ts.pop();
	    	i--;
	    }
		
	    //recuperar lOffset y pOffset anterior
	    for (i = ts.size() - 1; i >= 0; i--) {
	    	if (ts.get(i).getKind() == EntryTS.VAR) {
	    		this.lOffset = ts.get(i).getOffset();
	    		break;
	    	}
	    	else 
	    		if (i == 0)
	    			this.lOffset = 0;
	    }
	    		
	    for (i = ts.size() - 1; i >= 0; i--) {
	    	if (ts.get(i).getKind() == EntryTS.PARAM) {
	    		this.pOffset = ts.get(i).getOffset();
	    		break;
	    	}
	    	else 
	    		if (i == 0)
	    			this.pOffset = 0;
	    }
	    
		this.blockLevel--;
	}

	//otros metodos:
	
	@Override
	public String toString() {
		String cadena /* SymTable */ = " [blockLevel=" + blockLevel;
		cadena += "\ngOffset=" + gOffset + ", lOffset=" + lOffset + ", pOffset=" + pOffset;
		cadena += "\nts= \n";
		for (EntryTS entrada : ts)
			cadena += entrada + "\n";
		return cadena;
	}


	public int getBlockLevel() {
		return blockLevel;
	}


	public void setBlockLevel(int blockLevel) {
		this.blockLevel = blockLevel;
	}
	
	//Practica 16: asignacion de memoria
	public void printGlobal() { //allProgram: print tamaño de area global
		System.out.println("Tamaño de area global = " + gOffset);
	}
	
	public void printLocal() { //allFunc: print tamaño de area global
		System.out.println("Tamaño de area local de funcion actual = " + lOffset);
	}
	
	public EntryTS modifyOffset(String name, int offset) { //allVar: asigna desplazamiento a entrada de TS
		for (int i = ts.size() - 1; i >= 0; i--)
	    	if (ts.get(i).getName().equals(name)) {//si es la entrada encontrada
	    		EntryTS entrada = ts.get(i);
	    		entrada.setOffset(offset);
	    		ts.set(i, entrada);
	    		return entrada;
	    	}
	    	
		return null; //no encontrada
	}
		
}
